/*
	Copyright (C) 2010 2011 Rémi Thebault

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include <QPainter>
#include <QGraphicsSceneHoverEvent>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsSceneDragDropEvent>
#include <QPixmap>
#include <QByteArray>
#include <QDataStream>
#include <QDrag>
#include <QMimeData>
#include <QDragEnterEvent>
#include <QDragLeaveEvent>
#include <QDragMoveEvent>

#include "gantttask.h"
#include "ganttscene.h"
#include "ganttaxis.h"
#include "ganttmtask.h"
#include "ganttlink.h"
#include "ganttcycle.h"
#include "procycle.h"
#include "taskselectionmodel.h"


const QString GTASK_MIMETYPE = "application/x-procycle-gtask";


GanttTask::GanttTask(ProCycle* app, PC::Task* task, GanttCycle* gCycle, GanttMTask* parent) :
	GanttItem(app, gCycle, parent),
	m_task (task),
	m_lateFinish(0),
	m_drawCriticalPath(false),
	m_drawCycleOverlap(false),
	m_drawTaskFloats(false),
	m_drawTaskName(true),
	m_drawFromStart(false),
	m_drawFromFinish(false),
	m_drawToStart(false),
	m_drawToFinish(false),
	m_linksHovered(false)
{
	setZValue(2);
}


GanttTask::~GanttTask() {

}



QList<GanttLink*> GanttTask::inGLinks() {
	return gCycle()->inGLinksForGTask(this);
}

QList<GanttLink*> GanttTask::outGLinks() {
	return gCycle()->outGLinksForGTask(this);
}




void GanttTask::setDrawFromStart(const bool& draw) {
	if (m_drawFromStart != draw) {
		if (m_drawFromStart = draw) {
			m_drawFromFinish = false;
			m_drawToStart = false;
			m_drawToFinish = false;
		}
		update();
	}
}


void GanttTask::setDrawFromFinish(const bool& draw) {
	if (m_drawFromFinish != draw) {
		if (m_drawFromFinish = draw) {
			m_drawFromStart = false;
			m_drawToStart = false;
			m_drawToFinish = false;
		}
		update();
	}
}


void GanttTask::setDrawToStart(const bool& draw) {
	if (m_drawToStart != draw) {
		if (m_drawToStart = draw) {
			m_drawFromStart = false;
			m_drawFromFinish = false;
			m_drawToFinish = false;
		}
		update();
	}
}


void GanttTask::setDrawToFinish(const bool& draw) {
	if (m_drawToFinish != draw) {
		if (m_drawToFinish = draw) {
			m_drawFromStart = false;
			m_drawFromFinish = false;
			m_drawToStart = false;
		}
		update();
	}
}

void GanttTask::unsetAllDraws() {
	bool trigger = m_drawFromStart || m_drawFromFinish
			|| m_drawToStart || m_drawToFinish;
	m_drawFromStart = false;
	m_drawFromFinish = false;
	m_drawToStart = false;
	m_drawToFinish = false;
	if (trigger) {
		update();
	}
}



void GanttTask::setCycleProp(PC::Cycle::Property p, const QVariant& value) {
	switch(p) {
	case::PC::Cycle::PROP_DRAWCRITICAL:
		m_drawCriticalPath = value.toBool();
		break;
	case::PC::Cycle::PROP_DRAWOVERLAP:
		m_drawCycleOverlap = value.toBool();
		break;
	case::PC::Cycle::PROP_DRAWFLOATS:
		m_drawTaskFloats = value.toBool();
		break;
	case::PC::Cycle::PROP_DRAWTEXT:
		m_drawTaskName = value.toBool();
		break;
	}
}

void GanttTask::editModeChanged() {
	if (g_editMode == EDIT_TASK) {
		setZValue(3);
	}
	else {
		setZValue(2);
	}
	setAcceptHoverEvents(g_editMode != EDIT_NONE);
	setAcceptDrops(g_editMode == EDIT_LINK);
}

void GanttTask::setLinksHovered(bool hovered) {
	QList<GanttLink*> ins = inGLinks();
	foreach(GanttLink* gl, ins) {
		gl->setHovered(hovered);
	}
	QList<GanttLink*> outs = outGLinks();
	foreach(GanttLink* gl, outs) {
		gl->setHovered(hovered);
	}
	m_linksHovered = hovered;
}




void GanttTask::requestSelection(bool selected) {
	app()->selectionModel()->selectTask(m_task, selected);
}


const float smallratio = 0.3;
const float bigratio = 0.7;
const float overhead = 3;

QRectF GanttTask::fromStartRect() {
	return QRectF(m_rect.left()-overhead, m_rect.top(),
				  smallratio*m_rect.width() + overhead, m_rect.height());
}


QRectF GanttTask::fromFinishRect() {
	return QRectF(m_rect.left()+smallratio*m_rect.width(), m_rect.top(),
				  bigratio*m_rect.width() + overhead, m_rect.height());
}


QRectF GanttTask::toStartRect() {
	return QRectF(m_rect.left()-overhead, m_rect.top(),
				  bigratio*m_rect.width() + overhead, m_rect.height());
}


QRectF GanttTask::toFinishRect() {
	return QRectF(m_rect.left()+bigratio*m_rect.width(), m_rect.top(),
				  smallratio*m_rect.width() + overhead, m_rect.height());
}





void GanttTask::paint(QPainter *painter, const QStyleOptionGraphicsItem*, QWidget*) {
	GanttAxis* axis = m_app->ganttAxis();

	int s = axis->unit2px(m_task->start());
	int f = axis->unit2px(m_task->finish());
	m_lateFinish = axis->unit2px(m_task->lateFinish());

	bool co = m_drawCycleOverlap;
	bool tf = m_drawTaskFloats && !m_task->isCriticalPath();

	m_rect.setRect(s, barTop(), f-s, barHeight());


	if (tf) {
		paintFloat(painter);
	}

	paintTaskBar(painter);

	if (co) {
		painter->save();

		int offset = axis->unit2px(m_task->cycle()->length());
		painter->translate(offset, 0);

		paintTaskBar(painter);

		painter->restore();
	}

	paintTaskName(painter, (tf ? m_lateFinish : m_rect.right()) + 12, m_rect.bottom());

	if (m_drawFromStart) {
		paintFromStartDock(painter);
	}
	else if (m_drawFromFinish) {
		paintFromFinishDock(painter);
	}
	else if (m_drawToStart) {
		paintToStartDock(painter);
	}
	else if (m_drawToFinish) {
		paintToFinishDock(painter);
	}
}




void GanttTask::paintFloat(QPainter *painter) {
	painter->save();

	int left = m_lateFinish - 4;
	int ldy = linkDockY();

	if (left > m_rect.right()) {
		QLine float_ (m_rect.right(), ldy, left, ldy);
		QPen dashes (Qt::black);
		dashes.setStyle(Qt::DashLine);
		painter->setPen(dashes);
		painter->drawLine(float_);
	}
	QPainterPath triangle (QPoint(left, ldy));
	triangle.lineTo(m_lateFinish, ldy - 2);
	triangle.lineTo(m_lateFinish, ldy + 2);
	painter->setBrush(Qt::red);
	painter->setPen(Qt::red);
	painter->drawPath(triangle);

	painter->restore();
}



void GanttTask::paintTaskName(QPainter *painter, int left, int bottom) {
	painter->save();

	painter->setPen(Qt::black);
	painter->drawText(left, bottom, m_task->name());

	painter->restore();
}



void GanttTask::paintFromStartDock(QPainter *painter) {
	painter->save();

	QPen pen (QColor(0x79, 0x79, 0x79));
	pen.setWidth(2);
	painter->setPen(pen);
	painter->setBrush(QColor(0x72, 0xae, 0x6f));

	painter->drawRect(fromStartRect());

	painter->restore();
}

void GanttTask::paintFromFinishDock(QPainter *painter) {
	painter->save();

	QPen pen (QColor(0x79, 0x79, 0x79));
	pen.setWidth(2);
	painter->setPen(pen);
	painter->setBrush(QColor(0x72, 0xae, 0x6f));

	painter->drawRect(fromFinishRect());

	painter->restore();
}

void GanttTask::paintToStartDock(QPainter *painter) {
	painter->save();

	QPen pen (QColor(0x79, 0x79, 0x79));
	pen.setWidth(2);
	painter->setPen(pen);
	painter->setBrush(QColor(0x72, 0xae, 0x6f));

	painter->drawRect(toStartRect());

	painter->restore();
}

void GanttTask::paintToFinishDock(QPainter *painter) {
	painter->save();

	QPen pen (QColor(0x79, 0x79, 0x79));
	pen.setWidth(2);
	painter->setPen(pen);
	painter->setBrush(QColor(0x72, 0xae, 0x6f));

	painter->drawRect(toFinishRect());

	painter->restore();
}




void GanttTask::hoverMoveEvent(QGraphicsSceneHoverEvent *event) {
	GanttItem::hoverMoveEvent(event);
	if (g_editMode == EDIT_LINK) {
		if (pointOnFromFinish(event->pos())) {
			setDrawFromFinish(true);
		}
		else if (pointOnFromStart(event->pos())) {
			setDrawFromStart(true);
		}
		else {
			unsetAllDraws();
		}
	}
}

void GanttTask::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) {
	GanttItem::hoverLeaveEvent(event);
	unsetAllDraws();
}


void GanttTask::mousePressEvent(QGraphicsSceneMouseEvent *event) {
	GanttItem::mousePressEvent(event);
	if (g_editMode == EDIT_LINK) {
		if (pointOnFromFinish(event->pos())) {
			g_linkEditMode = LINKEDIT_FROMFINISH;
			g_linkEditTask = task();
		}
		else if (pointOnFromStart(event->pos())) {
			g_linkEditMode = LINKEDIT_FROMSTART;
			g_linkEditTask = task();
		}
	}
}



void GanttTask::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
	GanttItem::mouseMoveEvent(event);
	if (g_editMode == EDIT_LINK) {
		if ((event->pos() - m_pressPos).manhattanLength()
				< QApplication::startDragDistance()) {
			m_clickable = false;

			QPixmap pm (":/images/mode_link.png");

			QMimeData *mimeData = new QMimeData;
			mimeData->setData(GTASK_MIMETYPE, QByteArray());

			QDrag *drag = new QDrag(event->widget());
			drag->setMimeData(mimeData);
			drag->setPixmap(pm);
			drag->setHotSpot(QPoint(0, 0));

			if (drag->exec(Qt::MoveAction) != Qt::MoveAction) {
				// cancel here
			}
		}
	}
}

void GanttTask::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
	GanttItem::mouseReleaseEvent(event);
}


void GanttTask::dragEnterEvent(QGraphicsSceneDragDropEvent *event) {
	if (event->mimeData()->hasFormat(GTASK_MIMETYPE)) {
		event->acceptProposedAction();
	} else {
		event->ignore();
	}
}

void GanttTask::dragMoveEvent(QGraphicsSceneDragDropEvent *event) {
	if (event->mimeData()->hasFormat(GTASK_MIMETYPE)) {
		if (pointOnToStart(event->pos())) {
			setDrawToStart(true);
			setHovered(true);
		}
		else if (pointOnToFinish(event->pos())) {
			setDrawToFinish(true);
			setHovered(true);
		}
		else {
			unsetAllDraws();
			setHovered(false);
		}
	}
}

void GanttTask::dragLeaveEvent(QGraphicsSceneDragDropEvent *event) {
	if (event->mimeData()->hasFormat(GTASK_MIMETYPE)) {
		unsetAllDraws();
		setHovered(false);
	}
}

class BadLinking {};

void GanttTask::dropEvent(QGraphicsSceneDragDropEvent *event) {
	if (event->mimeData()->hasFormat(GTASK_MIMETYPE) &&
			g_editMode == EDIT_LINK && g_linkEditMode != LINKEDIT_NONE) {

		PC::Link::Type type;

		try {
			if (!g_linkEditTask) {
				throw BadLinking();
			}
			bool toStart = false;
			if (pointOnToStart(event->pos())) {
				toStart = true;
			}
			else if(!pointOnToFinish(event->pos())) {
				throw BadLinking();
			}
			if (toStart) {
				if (g_linkEditMode == LINKEDIT_FROMSTART) {
					type = PC::Link::TYPE_SS;
				}
				else if (g_linkEditMode == LINKEDIT_FROMFINISH) {
					type = PC::Link::TYPE_FS;
				}
				else {
					throw BadLinking();
				}
			}
			else {
				if (g_linkEditMode == LINKEDIT_FROMSTART) {
					type = PC::Link::TYPE_SF;
				}
				else if (g_linkEditMode == LINKEDIT_FROMFINISH) {
					type = PC::Link::TYPE_FF;
				}
				else {
					throw BadLinking();
				}
			}
		}
		catch (BadLinking) {
			event->ignore();
			return;
		}

		task()->cycle()->createLink(g_linkEditTask, task(), type);

		g_linkEditTask = 0;
		g_linkEditMode = LINKEDIT_NONE;

		event->acceptProposedAction();

	} else {
		event->ignore();
	}
}
